package com.faner4cloud.yun.common.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.core.util.StrUtil;
import com.faner4cloud.yun.common.util.R;
import com.faner4cloud.yun.common.util.RequestUtil;
import com.faner4cloud.yun.common.constant.enums.CodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {

    @Value("${spring.servlet.multipart.max-file-size:20M}")
    private String maxFileSize;

    @ExceptionHandler({Exception.class,ServletException.class})
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public R processException(Exception e) {
        log.error("[processException]全局异常Exception拦截 ex: [{}]", e);
        return throwFail(CodeEnum.FAIL.getCode(),CodeEnum.FAIL.getMessage());
    }


	/**
	 * 权限码异常
	 */
	@ExceptionHandler(NotPermissionException.class)
	public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
		String requestURI = request.getRequestURI();
		log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
		return throwFail(cn.hutool.http.HttpStatus.HTTP_FORBIDDEN, "没有访问权限，请联系管理员授权");
	}

	/**
	 * 角色权限异常
	 */
	@ExceptionHandler(NotRoleException.class)
	public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
		String requestURI = request.getRequestURI();
		log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
		return throwFail(cn.hutool.http.HttpStatus.HTTP_FORBIDDEN, "没有访问权限，请联系管理员授权");
	}

	/**
	 * 认证失败
	 */
	@ExceptionHandler(NotLoginException.class)
	public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
		String requestURI = request.getRequestURI();
		log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
		return throwFail(cn.hutool.http.HttpStatus.HTTP_UNAUTHORIZED, "认证失败，无法访问系统资源");
	}

    /**
     * HttpException
     */
    @ExceptionHandler({HttpException.class})
    public R processException(HttpException e, HttpServletRequest request, HttpServletResponse response) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        R r = new R();
        r.setPath(RequestUtil.getSimpleRequest(request));
        int code = e.getCode();
        r.setCode(code);
        response.setStatus(e.getHttpCode());
        String errorMessage = "";
        if (CodeEnum.getCodeEnum(code) != null){
            errorMessage = CodeEnum.getCodeEnum(code).getMessage();
        }
        if (StrUtil.isNotBlank(errorMessage) ) {
            r.setMsg(e.getMessage());
            log.error("自定义异常消息:{}, 位置:{}", e.getMessage(),e.getStackTrace()[0].toString());
        } else {
            r.setMsg(e.getMessage());
            log.error("", e.getClass().getConstructor(int.class, String.class).newInstance(CodeEnum.FAIL.getCode(), errorMessage));
        }
        return r;
    }

	@ExceptionHandler(MissingServletRequestParameterException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Object> handleError(MissingServletRequestParameterException e) {
		log.warn("缺少请求参数:{}", e.getMessage());
		String message = String.format("缺少必要的请求参数: %s", e.getParameterName());
		return throwFail(CodeEnum.PARAM_MISS.getCode(),message);
	}

	@ExceptionHandler({MethodArgumentTypeMismatchException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Object> handleError(MethodArgumentTypeMismatchException e) {
		log.warn("请求参数格式错误:{}", e.getMessage());
		String message = String.format("请求参数格式错误: %s", e.getName());
		return throwFail(CodeEnum.PARAM_TYPE_ERROR.getCode(),message);
	}

	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Object> handleError(MethodArgumentNotValidException e) {
		log.warn("参数验证失败:{}", e.getMessage());
		return handleError(e.getBindingResult());
	}

	@ExceptionHandler(BindException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Object> handleError(BindException e) {
		log.warn("参数绑定失败:{}", e.getMessage());
		return handleError(e.getBindingResult());
	}

	@ExceptionHandler(ConstraintViolationException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Object> handleError(ConstraintViolationException e) {
		log.warn("参数验证失败:{}", e.getMessage());
		return handleError(e.getConstraintViolations());
	}

	@ExceptionHandler(NoHandlerFoundException.class)
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public R<Object> handleError(NoHandlerFoundException e) {
		log.error("404没找到请求:{}", e.getMessage());
		return throwFail(CodeEnum.NOT_FOUND.getCode(),e.getMessage());
	}

	@ExceptionHandler(HttpMessageNotReadableException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<Object> handleError(HttpMessageNotReadableException e) {
		log.error("消息不能读取:{}", e.getMessage());
		return throwFail(CodeEnum.MSG_NOT_READABLE.getCode(),e.getMessage());
	}

	@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
	@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
	public R<Object> handleError(HttpRequestMethodNotSupportedException e) {
		log.error("不支持当前请求方法:{}", e.getMessage());
		return throwFail(CodeEnum.METHOD_NOT_SUPPORTED.getCode(),e.getMessage());
	}

	@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
	@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
	public R<Object> handleError(HttpMediaTypeNotAcceptableException e) {
		String message = e.getMessage() + " " + StrUtil.join(",",e.getSupportedMediaTypes());
		log.error("不支持当前媒体类型:{}", message);
		return throwFail(CodeEnum.MEDIA_TYPE_NOT_SUPPORTED.getCode(),message);
	}



	/**
	 * 处理 BindingResult
	 *
	 * @param result BindingResult
	 * @return R
	 */
	private R handleError(BindingResult result) {
		FieldError error = result.getFieldError();
		String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
		return throwFail(CodeEnum.PARAM_BIND_ERROR.getCode(), message);
	}

	/**
	 * 处理 ConstraintViolation
	 *
	 * @param violations 校验结果
	 * @return R
	 */
	private R<Object> handleError(Set<ConstraintViolation<?>> violations) {
		ConstraintViolation<?> violation = violations.iterator().next();
		String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
		String message = String.format("%s:%s", path, violation.getMessage());
		return throwFail(CodeEnum.PARAM_VALID_ERROR.getCode(), message);
	}


	/**
     * TypeMismatchException BeanUtil.copy 赋值 名称相同类型不同 赋值错误
     */
    @ExceptionHandler({TypeMismatchException.class})
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(TypeMismatchException e) {
		log.error("赋值名称相同类型不同赋值错误:{}", e.getMessage());
		return throwFail(CodeEnum.PARAM_TYPE_ERROR.getCode(), e.getMessage());
    }

	/**
	 * 处理业务校验过程中碰到的非法参数异常 该异常基本由{@link org.springframework.util.Assert}抛出
	 * @see Assert#hasLength(String, String)
	 * @see Assert#hasText(String, String)
	 * @see Assert#isTrue(boolean, String)
	 * @see Assert#isNull(Object, String)
	 * @see Assert#notNull(Object, String)
	 * @param exception 参数校验异常
	 * @return API返回结果对象包装后的错误输出结果
	 */
	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseStatus(HttpStatus.OK)
	public R handleIllegalArgumentException(IllegalArgumentException exception) {
		log.error("非法参数,ex = {}", exception.getMessage(), exception);
		return R.failed(exception.getMessage());
	}


	private R throwFail(int code, String msg){
		return R.builder()
			.code(code)
			.msg(msg)
			.path(RequestUtil.getSimpleRequest())
			.build();
	}
}
